using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections;
using System.Reflection;
using System.Security.Permissions;
using System.ComponentModel;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Data;
using System.Data.Odbc;
using NumericalRecipes;
using BmgNet;
using StaticUtility;

namespace ObjectLib
{
    /// <summary>
    /// Converts file names between relative and absolute paths.
    /// </summary>
    [Serializable]
    public class FileRelativizor : ICloneable
    {
        /// <summary>
        /// The value of this.AbsoluteName before the most recent call to this.Absolutize.
        /// </summary>
        public string PreviousAbsoluteName
        {
            get
            {
                return previousAbsoluteName;
            }
        }
        protected string previousAbsoluteName = null;
        /// <summary>
        /// The absolute file path.
        /// </summary>
        public string AbsoluteName = null;
        /// <summary>
        /// The relative file path, computed by Relativize.
        /// </summary>
        public string RelativeName = null;
        public FileRelativizor() { }
        /// <summary>
        /// Creates the object with the AbsoluteName given.  The RelativeName is not computed
        /// until Relativize is called.
        /// </summary>
        /// <param name="AbsoluteName"></param>
        public FileRelativizor(string AbsoluteName)
        {
            this.AbsoluteName = AbsoluteName;
        }
        /// <summary>
        /// Computes the absolute path from RelativeName and relativeTo.  Stores the old value of AbsoluteName
        /// as previousAbsoluteName;
        /// </summary>
        /// <param name="relativeTo">A path and filename, tells which file RelativeName is relative to.</param>
        public void Absolutize(string relativeTo)
        {
            previousAbsoluteName = AbsoluteName;
            AbsoluteName = FileRelativizor.Absolutize(relativeTo, RelativeName);
        }
        /// <summary>
        /// Converts the image name to a path relative to the project file name.
        /// </summary>
        /// <param name="relativeTo">A path and filename, tells which file RelativeName is relative to.</param>
        public void Relativize(string relativeTo)
        {
            RelativeName = FileRelativizor.Relativize(relativeTo, AbsoluteName);
        }
        /// <summary>
        /// Outputs a string that represents the relative path of relatedFrom with respect to relatedTo.
        /// </summary>
        /// <param name="relatedTo">string  An absolute file path.</param>
        /// <param name="relatedFrom">string  An absolute file path.</param>
        /// <returns>string The relative file path of relatedFrom with respect to relatedTo.</returns>
        public static string Relativize(string relatedTo, string relatedFrom)
        {
            //	Use the same slash character to make things easier.
            relatedTo = relatedTo.Replace('/', '\\');
            relatedFrom = relatedFrom.Replace('/', '\\');

            //	Find the first '/' or '\\' in each string.
            int iTo = relatedTo.Length - 1;
            while (iTo > 0 && relatedTo[iTo] != '\\')
                iTo--;
            int iFrom = relatedFrom.Length - 1;
            while (iFrom > 0 && relatedFrom[iFrom] != '\\')
                iFrom--;

            //	Walk forwards, at most to min(iTo, iFrom), in order to determine how far the similarity goes.
            int j = 0;
            int lastSep = 0;
            while (j <= iTo && j <= iFrom && relatedTo[j] == relatedFrom[j])
            {
                if (relatedTo[j] == '\\')
                    lastSep = j;
                j++;
            }

            //	If lastSep==iTo, then the answer is relatedFrom.Substring(j+1);
            if (lastSep == iTo)
                return relatedFrom.Substring(lastSep + 1);
            //	Otherwise, we can start with this answer, but we must add "..\\" for each additional directory in which 
            //	relatedTo is buried inside
            else
            {
                int jStart = j = lastSep + 1;
                int jEnd = iTo;
                int ct = 0;
                //	count the number of '\\' characters in this series.
                for (j = jStart; j <= jEnd; j++)
                    if (relatedTo[j] == '\\')
                        ct++;
                string output = "";
                for (j = 0; j < ct; j++)
                    output = output + "..\\";
                return output + relatedFrom.Substring(lastSep + 1);
            }
        }
        /// <summary>
        /// Outputs a string that represents the absolute path of relatedFrom with respect to relatedTo.
        /// </summary>
        /// <param name="relatedTo">string  An absolute file path.</param>
        /// <param name="relatedFrom">string  A relative file path, not led by a '/' or '\\'.</param>
        /// <returns>string The absolute file path of relatedFrom with respect to relatedTo.</returns>
        public static string Absolutize(string relatedTo, string relatedFrom)
        {
            //	Use the same slash character to make things easier.
            relatedTo = relatedTo.Replace('/', '\\');
            relatedFrom = relatedFrom.Replace('/', '\\');

            if (relatedFrom[0] == '\\')
                throw new ArgumentException("Parameter cannot be led by a / or \\ character", "relatedFrom");

            //	Find the first '/' or '\\' in relatedTo.
            int iTo = relatedTo.Length - 1;
            while (iTo > 0 && relatedTo[iTo] != '\\')
                iTo--;

            //	Find the first "..\\" in relatedFrom
            int iFrom = relatedFrom.Length - 1;
            while (iFrom > 1 && relatedFrom.Substring(iFrom - 2, 3) != "..\\")
                iFrom--;

            //	If there is no "..\\" in iFrom, then we are done.
            if (iFrom <= 1)
                return relatedTo.Substring(0, iTo) + '\\' + relatedFrom;

            //	Capture the part to the right of any "..\\" sequences.
            string rhs = relatedFrom.Substring(iFrom + 1);

            //	For each "..\\" sequence, reduce iTo accordingly.
            while (iFrom > 1)
            {
                //	Bring it back one more space, so it signifies the end of the path to the left of a '\\'.
                iTo--;

                if (relatedFrom.Substring(iFrom - 2, 3) == "..\\")
                {
                    iFrom -= 3;
                    while (iTo > 0 && relatedTo[iTo] != '\\')
                        iTo--;
                }
                else
                    throw new ArgumentException("String parameter is not formatted correctly.  Left hand side has characters before ..\\", "relatedFrom");
                if (iTo == 0 && iFrom > 1)
                    throw new ArgumentException("Parameter was not formatted consistently with relatedFrom.  It may not be an absolute path.", "relatedTo");
            }
            if (iFrom >= 0)
                throw new ArgumentException("String parameter is not formatted correctly.", "relatedFrom");
            return relatedTo.Substring(0, iTo) + '\\' + rhs;
        }
        /// <summary>
        /// Creates a deep clone of this instance, also cloning all strings contained herein.
        /// </summary>
        /// <returns>A deep clone.</returns>
        public object Clone()
        {
            FileRelativizor fr;
            if (previousAbsoluteName == null)
            {
                fr = new FileRelativizor(AbsoluteName);
                fr.RelativeName = this.RelativeName;
                return fr;
            }
            fr = new FileRelativizor(previousAbsoluteName);
            fr.Relativize(AbsoluteName);
            fr.AbsoluteName = AbsoluteName;
            fr.RelativeName = this.RelativeName;
            return fr;
        }
    }
	#region Database
	/// <summary>
	/// Encapsulates ODBC connectivity.
	/// </summary>
	[Serializable]
	public class DbSource : ISerializable
	{
		[NonSerialized]
		public static readonly string FileName = "Databases.DbSource";
		[NonSerialized]
		public OdbcConnection Connection;
		[NonSerialized]
		public ArrayList ConnectionLog;
		[NonSerialized]
		public DataSet DbSet;
		/// <summary>
		/// A meaningless label for the user to recognize which database is which.
		/// </summary>
		public string Label="Default";
		/// <summary>
		/// The name of the database driver.
		/// </summary>
		public string Driver = "{MySQL ODBC 3.51 Driver}";
		/// <summary>
		/// The name of the database;
		/// </summary>
		public string Database = "Pod";
		/// <summary>
		/// The server.
		/// </summary>
		public string Server = "localhost";
		/// <summary>
		/// The name of the user.
		/// </summary>
		public string User = "root";
		/// <summary>
		/// The password.
		/// </summary>
		public string Password = "connect";
		/// <summary>
		/// The option.
		/// </summary>
		public int Option = 3;
		/// <summary>
		/// The string used to connect to the database.
		/// </summary>
		public string ConnectString
		{
			get
			{
				return "DRIVER=" + Driver + 
					"; SERVER=" + Server + 
					"; DATABASE=" + Database + 
					"; UID=" + User + 
					"; PASSWORD=" + Password + 
					"; OPTION=" + Convert.ToString(Option) + ";";
			}
		}
		/// <summary>
		/// Constructs a DbSource with default connection string.
		/// </summary>
		public DbSource()
		{
			ConnectionLog = new ArrayList();
			DbSet = new DataSet( Database );
		}
		public DbSource(SerializationInfo info, StreamingContext context) : this()
		{
			Label = info.GetString("Label");
			Driver = info.GetString("Driver");
			Server = info.GetString("Server");
			User = info.GetString("User");
			Password = info.GetString("Password");
			Option = info.GetInt32("Option");
		}
		public void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			info.AddValue("Label", Label);
			info.AddValue("Driver", Driver);
			info.AddValue("Server", Server);
			info.AddValue("User", User);
			info.AddValue("Password", Password);
			info.AddValue("Option", Option);
		}
		/// <summary>
		/// Constructs a DbSource with a connection string constructed from the arguments of this constructor.
		/// </summary>
		/// <param name="Driver">Set null for default.</param>
		/// <param name="Database">Set null for default.</param>
		/// <param name="Server">Set null for default.</param>
		/// <param name="User">Set null for default.</param>
		/// <param name="Password">Set null for default.</param>
		/// <param name="Option">Set below zero for default.</param>
		public DbSource(string Driver, string Database, string Server, string User, string Password, int Option)
		{
			if(Driver != null)
				this.Driver = Driver;
			if(Database != null)
				this.Database = Database;
			if(Server != null)
				this.Server = Server;
			if(User != null)
				this.User = User;
			if(Password != null)
				this.Password = Password;
			if(Option >=0)
				this.Option = Option;
			ConnectionLog = new ArrayList();
			DbSet = new DataSet( Database );
		}
		/// <summary>
		/// Adds a data table to the data set.
		/// </summary>
		/// <param name="table"></param>
		public void DbSetAdd(DataTable table)
		{
			DbSet.Tables.Add(table);
		}
		/// <summary>
		/// Opens a connection (closes the existing one if necessary).
		/// </summary>
		/// <returns>Returns null if the operation succeeds.  Otherwise, the Exception is returned.</returns>
		public Exception OpenConnection()
		{	
			Exception ex = null;
			try
			{
				if(Connection != null)
				{
					ConnectionLog.Add("The previous connection was in the state: "
						+ Enum.GetName(typeof(ConnectionState),Connection.State) + 
						".  It's connection string was: " + Connection.ConnectionString);

					Connection.Close();
					ConnectionLog.Add("Connection was closed successfully.");
				}
			}
			catch(Exception exx)
			{
				ex = exx;
				ConnectionLog.Add("There was a problem closing the previous OdbcConnection.  "
					+ "The following exception was generated:  " + ex.Message);
			}
			if(ex==null)
			{
				try
				{

					Connection = new OdbcConnection(ConnectString);
					ConnectionLog.Add("A new OdbcConnection was created.  It's connection string is:  " + ConnectString);

					Connection.Open();
					ConnectionLog.Add("The connection was opened successfully.");
				}
				catch(Exception exxx)
				{
					ex = exxx;
					ConnectionLog.Add("There was a problem opening the new OdbcConnection.  "
						+ "The following exception was generated:  " + ex.Message);
				}
			}
			return ex;
		}
		public override string ToString()
		{
			if(Label==null)
				return "";
			return Label;
		}
		
	}
	public abstract class DbTable
	{	
		/// <summary>
		/// Empty constructor.  Does not enable connection to an actual database.  Does not initialize
		/// any data adapters.  Simply generates data tables.
		/// </summary>
		public DbTable()
		{
		}
		/// <summary>
		/// Constructor enabling connection to an actual database (via the DbSource object).
		/// </summary>
		public DbTable(DbSource Connection)
		{
			this.Source = Connection;
		}
		/// <summary>
		/// Child classes have already assigned ColumnTypes and ColumnParams.
		/// </summary>
		/// <param name="tableName"></param>
		/// <param name="primaryKeyColumns"></param>
		public void InitializeTable(string tableName, int[] PrimaryKeyColumns)
		{
			int i;

			//	Create the table
			Table = new DataTable(tableName);
			for(i=0; i<ColumnTypes.Length; i++)
			{
				DataColumn newCol = new DataColumn(ColumnParams[i].ParameterName);
				Util.SetDataColumnType(newCol, ColumnTypes[i]);
				Table.Columns.Add(newCol);
			}

			//	Assign the primary key columns
			this.PrimaryKeyColumns = PrimaryKeyColumns;
			DataColumn[] primKey = new DataColumn[PrimaryKeyColumns.Length];
			for(i=0; i<PrimaryKeyColumns.Length; i++)
				primKey[i] = Table.Columns[PrimaryKeyColumns[i]];
			Table.PrimaryKey = primKey;
		}
		/// <summary>
		/// Initializes the data adapter that will sync the DataTable in memory with the SQL database.  This adapter is for
		/// selecting all information from the table or for inserting full rows into the table.
		/// </summary>
		public virtual void InitializeAdapters()
		{
			int i;

			if(AllAdapter == null)
			{
				//	SELECT
				AllAdapter = new OdbcDataAdapter("Select * From " + Table.TableName + ";", Source.Connection);
				for(i=0; i<ColumnParams.Length; i++)
					AllAdapter.SelectCommand.Parameters.Add( Util.OdbcParameterCopy(ColumnParams[i]) );
				//	INSERT
				AllAdapter.InsertCommand = new OdbcCommand("Insert Into " + Table.TableName + '('+CommaSepFieldNames+')' + 
					"Values(" + Util.CommaSepQuestionMarks(ColumnParams.Length) + ");", Source.Connection );
				for(i=0; i<ColumnParams.Length; i++)
					AllAdapter.InsertCommand.Parameters.Add( Util.OdbcParameterCopy(ColumnParams[i]) );
				//	UPDATE
				AllAdapter.UpdateCommand = new OdbcCommand("Update " + Table.TableName + '('+CommaSepFieldNames+')' + 
					"Values(" + Util.CommaSepQuestionMarks(ColumnParams.Length) + ");", Source.Connection );
				for(i=0; i<ColumnParams.Length; i++)
					AllAdapter.UpdateCommand.Parameters.Add( Util.OdbcParameterCopy(ColumnParams[i]) );
				//	DELETE
				AllAdapter.DeleteCommand = new OdbcCommand("Delete * From " +Table.TableName+ ";", Source.Connection);
				for(i=0; i<ColumnParams.Length; i++)
					AllAdapter.DeleteCommand.Parameters.Add( Util.OdbcParameterCopy(ColumnParams[i]) );
			}
		}
		public int InsertAll()
		{
			int i,j, output=0;

			//	Initialize the adapter if necessary.  (This should have been done in the constructor already.)
			if( AllAdapter == null )
				InitializeAdapters();

			//	For each row, set the values and insert it into the database.
			for(i=0; i<Table.Rows.Count; i++)
			{
				for(j=0; j<ColumnParams.Length; j++)
					AllAdapter.InsertCommand.Parameters[j].Value = Table.Rows[i][j];
				//	Execute the INSERT statement
				output += AllAdapter.InsertCommand.ExecuteNonQuery();
			}

			return output;
		}
		/// <summary>
		/// Contains the SQL string that defines a column's data type.
		/// </summary>
		public string[] ColumnTypes;
		/// <summary>
		/// Information about the type of parameter in a column.
		/// </summary>
		public OdbcParameter[] ColumnParams;
		/// <summary>
		/// The ODBC connection.
		/// </summary>
		public DbSource Source;
		/// <summary>
		/// Represents the data table's architecture.
		/// </summary>
		public DataTable Table;
		/// <summary>
		/// Inserts full items into table.
		/// </summary>
		public OdbcDataAdapter AllAdapter;
		/// <summary>
		/// Zero-based index of the primary key column.
		/// </summary>
		public int[] PrimaryKeyColumns=new int[0];
		/// <summary>
		/// USAGE:  When creating a table, give SQL a string computed as:  "Create Table [this.SqlInsertTableString];"
		/// </summary>
		public string SqlCreateTableString
		{
			get
			{
				if(ColumnParams==null || ColumnParams.Length<=0)
					return "";
				string output = Table.TableName + " (";
				string sep = ", ";
				int i;
				for(i=0; i<ColumnParams.Length; i++)
				{
					if(i==ColumnParams.Length-1 && PrimaryKeyColumns.Length<=0)
						sep = "";
					output = output + "\n\t" + ColumnParams[i].ParameterName + " " + ColumnTypes[i] + sep;
				}
				if(PrimaryKeyColumns.Length>=0)
				{
					for(i=0; i<PrimaryKeyColumns.Length; i++)
					{
						output = output + "\n\tPrimary Key(" + ColumnParams[PrimaryKeyColumns[i]].ParameterName + ")";
						if(i<PrimaryKeyColumns.Length-1)
							output = output + ", ";
					}
					output = output + ");";
				}
				else
					output = output + ")";
				return output;
			}
		}
		/// <summary>
		/// USAGE:  When inserting into a table, give SQL a string computed as:
		/// "Insert Into [table name] ( [this.CommaSepFieldNames] ) values ( [comma separated values] );"
		/// </summary>
		public string CommaSepFieldNames
		{
			get
			{
				if(ColumnParams==null || ColumnParams.Length<=0)
					return "";
				string output = ColumnParams[0].ParameterName;
				for(int i=1; i<ColumnParams.Length; i++)
					output = output + ", " + ColumnParams[i].ParameterName;
				return output;
			}
		}
		/// <summary>
		/// USAGE:  When inserting into a table, give SQL a string computed as:
		/// "Insert Into [table name] ( [this.CommaSepFieldNames] ) values ( [comma separated values] );"
		/// </summary>
		public string CommaSepFieldNamesSet
		{
			get
			{
				if(ColumnParams==null || ColumnParams.Length<=0)
					return "";
				string output = ColumnParams[0].ParameterName + "=?";
				for(int i=1; i<ColumnParams.Length; i++)
					output = output + ", " + ColumnParams[i].ParameterName + "=?";
				return output;
			}
		}
		/// <summary>
		/// The command for creating the table.
		/// </summary>
		public OdbcCommand CreateTable
		{
			get
			{
				Source.ConnectionLog.Add("Create Table " + Table.TableName + ";");
				return new OdbcCommand( "Create Table " + SqlCreateTableString, Source.Connection );
			}
		}
		/// <summary>
		/// the command for dropping the table if it exists.
		/// </summary>
		public OdbcCommand DropTableIfExists
		{
			get
			{
				Source.ConnectionLog.Add("Drop Table If Exists " + Table.TableName + ";");
				return new OdbcCommand( "Drop Table If Exists " + Table.TableName + ";", Source.Connection );
			}
		}
	}
	public class ImageDbTable : DbTable
	{
		public static string[] ImageColumnTypes;
		public static OdbcParameter[] ImageColumnParams;
		static ImageDbTable()
		{
			ImageColumnParams = new OdbcParameter[]
				{
					new OdbcParameter("Id", OdbcType.Int, 0, "Id"),
					new OdbcParameter("Name", OdbcType.VarChar, 255, "Name"),	//	ObjectParserData.ShortProjname
					new OdbcParameter("ObjParserFileName", OdbcType.VarChar, 255, "ObjParserFileName"),		//	*.ObjParser filename (including extension)
					new OdbcParameter("ImageFileName", OdbcType.VarChar, 255, "ImageFileName"),			//	*.Png filename (including extension)
					new OdbcParameter("Width", OdbcType.Int, 0, "Width"),					//	Width of the image
					new OdbcParameter("Height", OdbcType.Int, 0, "Height"),					//	Height
					new OdbcParameter("DateTime", OdbcType.DateTime, 0, "DateTime"),		//	Local date and time at which image was taken.
					new OdbcParameter("NumObjects", OdbcType.Int, 0, "NumObjects"),				//	The number of objects defined within the image.
					new OdbcParameter("Exposure", OdbcType.Real, 0, "Exposure"), 
					new OdbcParameter("Fstop", OdbcType.Real, 0, "Fstop"),
					new OdbcParameter("Gps", OdbcType.VarChar, 255, "Gps"),
					new OdbcParameter("Location", OdbcType.VarChar, 255, "Location"),
					new OdbcParameter("Category", OdbcType.VarChar, 255, "Category"),
					new OdbcParameter("Sky", OdbcType.Int, 0, "Sky"),
					new OdbcParameter("Water", OdbcType.Int, 0, "Water"),
					new OdbcParameter("Foliage", OdbcType.Int, 0, "Foliage"),
					new OdbcParameter("DirtOrMud", OdbcType.Int, 0, "DirtOrMud"),
					new OdbcParameter("Rock", OdbcType.Int, 0, "Rock"),
					new OdbcParameter("Animal", OdbcType.Int, 0, "Animal"),
					new OdbcParameter("Manmade", OdbcType.Int, 0, "Manmade"),
					new OdbcParameter("People", OdbcType.Int, 0, "People"),
					new OdbcParameter("Descriptors", OdbcType.VarChar, 255, "Descriptors"),
					new OdbcParameter("RoiX", OdbcType.Real, 0, "RoiX"),
					new OdbcParameter("RoiY", OdbcType.Real, 0, "RoiY"),
					new OdbcParameter("RoiRadius", OdbcType.Real, 0, "RoiRadius")
				};
			ImageColumnParams[0].IsNullable = false;
			for(int i=0; i<ImageColumnParams.Length; i++)
				ImageColumnParams[i].Direction = ParameterDirection.InputOutput;

			ImageColumnTypes = new string[] {
												"int unsigned not null",
												"varchar(255)",
												"varchar(255)",
												"varchar(255)",
												"int",
												"int",
												"DateTime",
												"int",
												"float", 
												"float",
												"varchar(255)",
												"varchar(255)",
												"varchar(255)",
												"TinyInt",
												"TinyInt",
												"TinyInt",
												"TinyInt",
												"TinyInt",
												"TinyInt",
												"TinyInt",
												"TinyInt",
												"varchar(255)",
												"float",
												"float",
												"float"
											};
		}
		public ImageDbTable() : base()
		{
			ColumnParams = ImageColumnParams;
			ColumnTypes = ImageColumnTypes;

			//	Create table and assign primary key columns
			InitializeTable("Img", new int[] {0});
		}
		public ImageDbTable(DbSource Connection) : base(Connection)
		{
			ColumnParams = ImageColumnParams;
			ColumnTypes = ImageColumnTypes;

			//	Create table and assign primary key columns
			InitializeTable("Img", new int[] {0});
			
			//	Initialize the data adapter
			InitializeAdapters();
		}
		public DataRow AddDataRow( ObjectParserData opd )
		{
			DataRow row = Table.NewRow();
			InsertDataIntoRow( row, opd );
			Table.Rows.Add(row);
			row.AcceptChanges();
			return row;
		}
		public void InsertDataIntoRow( DataRow row, ObjectParserData opd)
		{			
			//"Id",						//	Uniquely assigned by the calling batch processor.
			row[0] = 0;
			//"Name",					//	ObjectParserData.ShortProjname
			row[1] = opd.ShortProjname;
			//"ObjParserFileName",		//	*.ObjParser filename (including extension)
			row[2] = opd.ShortProjname + ".ObjParser";
			//"ImageFileName",			//	*.Png filename (including extension)
			row[3] = Util.RemovePathInfo(opd.Imagename);
			//"Width",					//	Width of the image
			row[4] = opd.ImageWidth;
			//"Height",					//	Height
			row[5] = opd.ImageHeight;
			//"Time",					//	Local time at which image was taken.
			if(opd.CapturedAt==DateTime.MinValue)
				row[6] = System.DBNull.Value;
			else
				row[6] = opd.CapturedAt;
			//"NumObjects",				//	The number of objects defined within the image.
			row[7] = opd.NumObjects;
			//"Exposure", 
			row[8] = opd.ShutterRate;
			//"Fstop",
			row[9] = opd.Fstop;
			//"Gps",
			row[10] = opd.Gps;
			//"Location",
			row[11] = opd.Location;
			//"Category",
			row[12] = Enum.GetName(typeof(ImageCategory), opd.ImageCat.ImageCategory);
			//"Sky",
			row[13] = opd.ImageCat.Sky;
			//"Water",
			row[14] = opd.ImageCat.Water;
			//"Foliage",
			row[15] = opd.ImageCat.Foliage;
			//"DirtOrMud",
			row[16] = opd.ImageCat.DirtOrMud;
			//"Rock",
			row[17] = opd.ImageCat.Rock;
			//"Animal",
			row[18] = opd.ImageCat.Animals;
			//"Manmade",
			row[19] = opd.ImageCat.ManmadeObjects;
			//"People",
			row[20] = opd.ImageCat.People;
			//"Descriptors"
			row[21] = opd.SearchText;
			if(opd.Circles==null || opd.Circles.Length==0)
			{
				//"RoiX"
				row[22] = System.DBNull.Value;
				//"RoiY"
				row[23] = System.DBNull.Value;
				//"RoiRadius"
				row[24] = System.DBNull.Value;
			}
			else
			{
				if(opd.ActiveCircleInd>=opd.Circles.Length || opd.ActiveCircleInd<0)
					opd.ActiveCircleInd=0;
				//"RoiX"
				row[22] = opd.Circles[opd.ActiveCircleInd].Center.X;
				//"RoiY"
				row[23] = opd.Circles[opd.ActiveCircleInd].Center.Y;
				//"RoiRadius"
				row[24] = opd.Circles[opd.ActiveCircleInd].Radius;
			}
		}						
	}
	public class ParsedObjectDbTable : DbTable
	{
		public static string[] PobjColumnTypes;
		public static OdbcParameter[] PobjColumnParams;
		static ParsedObjectDbTable()
		{
			PobjColumnParams = new OdbcParameter[]
			{
				new OdbcParameter("Id", OdbcType.Int, 0, "Id"),
				new OdbcParameter("ImgId", OdbcType.Int, 0, "ImgId"),
				new OdbcParameter("Name", OdbcType.VarChar, 255, "Name"),
				new OdbcParameter("Category", OdbcType.VarChar, 255, "Category"),
				new OdbcParameter("MaskFileName", OdbcType.VarChar, 255, "MaskFileName"),
				new OdbcParameter("X", OdbcType.Int, 0, "X"),
				new OdbcParameter("Y", OdbcType.Int, 0, "Y"),
				new OdbcParameter("Width", OdbcType.Int, 0, "Width"),
				new OdbcParameter("Height", OdbcType.Int, 0, "Height"),
				new OdbcParameter("NumPixels", OdbcType.Int, 0, "NumPixels"),
				new OdbcParameter("NumPolygons", OdbcType.Int, 0,  "NumPolygons"),
				new OdbcParameter("SegmentationQuality", OdbcType.TinyInt, 0, "SegmentationQuality"),
				new OdbcParameter("Focus", OdbcType.TinyInt, 0, "Focus"),
				new OdbcParameter("Specularized", OdbcType.TinyInt, 0, "Specularized"),
				new OdbcParameter("PigmentVariation", OdbcType.TinyInt, 0, "PigmentVariation"),
				new OdbcParameter("Liveliness", OdbcType.TinyInt, 0, "Liveliness"),
				new OdbcParameter("SurfaceTexture", OdbcType.TinyInt, 0, "SurfaceTexture"),
				new OdbcParameter("Occluded", OdbcType.TinyInt, 0, "Occluded"),
				new OdbcParameter("SharpShadow", OdbcType.TinyInt, 0, "SharpShadow"),
				new OdbcParameter("Descriptors", OdbcType.Text, 65535, "Descriptors")
			};
			PobjColumnParams[0].IsNullable = false;
			PobjColumnParams[1].IsNullable = false;
			for(int i=0; i<PobjColumnParams.Length; i++)
				PobjColumnParams[i].Direction = ParameterDirection.InputOutput;
			
			PobjColumnTypes = new string[] {
											   "int unsigned not null",
											   "int unsigned not null References Img(Id)",
											   "varchar(255)",
											   "varchar(255)", 
											   "varchar(255)", 
											   "int",
											   "int",
											   "int", 
											   "int", 
											   "int", 
											   "int",
											   "TinyInt", 
											   "TinyInt", 
											   "TinyInt", 
											   "TinyInt", 
											   "TinyInt", 
											   "TinyInt", 
											   "TinyInt", 
											   "TinyInt", 
											   "Text"
										   };
		}
		public ParsedObjectDbTable()
		{
			//	Assign the ColumnTypes and ColumnParams
			ColumnParams = PobjColumnParams;
			ColumnTypes = PobjColumnTypes;

			//	Create table and assign primary key columns
			InitializeTable("Obj", new int[] {0});
		}
		public ParsedObjectDbTable(DbSource Connection) : base(Connection)
		{
			//	Assign the ColumnTypes and ColumnParams
			ColumnParams = PobjColumnParams;
			ColumnTypes = PobjColumnTypes;

			//	Create table and assign primary key columns
			InitializeTable("Obj", new int[] {0});

			//	Initialize the data adapter
			InitializeAdapters();
		}
		public DataRow AddDataRow( ParsedObject po, uint imageId )
		{
			DataRow row = Table.NewRow();
			InsertDataIntoRow( row, po, imageId );
			Table.Rows.Add(row);
			row.AcceptChanges();
			return row;
		}
		public void InsertDataIntoRow( DataRow row, ParsedObject po, uint imageId )
		{
			//"Id",
			row[0] = po.DbCache_UniqueId;
			//"ImageId",
			row[1] = imageId;
			//"Name",					//	the full name within ObjParser (including the category names).
			row[2] = po.Category.Path + "/" + po.Name;
			//"Category",
			row[3] = po.Category.Name;
			//"MaskFileName"
			row[4] = po.DbCache_MaskFileName;
			//	bounding rectangle
			Rectangle rect = po.BoundingRect;
			//"Left",
			row[5] = rect.Left;
			//"Top",
			row[6] = rect.Top;
			//"Width",
			row[7] = rect.Width;
			//"Height",
			row[8] = rect.Height;
			//"NumPixels",
			row[9] = po.Area;
			//"NumPolygons"
			row[10] = po.Polygons.Count;
			//"SegmentationQuality",
			row[11] = po.CatData.Quality;
			//"Focus",
			row[12] = po.CatData.Focused;
			//"Specularized",
			row[13] = po.CatData.Specularized;
			//"PigmentVariation",
			row[14] = po.CatData.UniformToVariegated;
			//"Liveliness",
			row[15] = po.CatData.DeadToLiving;
			//"SurfaceTexture",
			row[16] = po.CatData.SmoothToRough;
			//"Occluded",
			row[17] = po.CatData.Occluded;
			//"SharpShadow",
			row[18] = po.CatData.SharpShadowBoundary;
			//"Descriptors"
			row[19] = po.SearchText;
		}
	}
	public class PolygonDbTable : DbTable
	{
		public static string[] PolygonColumnTypes;
		public static OdbcParameter[] PolygonColumnParams;
		static PolygonDbTable()
		{
			PolygonColumnTypes = new string[] {
												  "int unsigned not null",
												  "int unsigned not null References Obj(Id)",
												  "int unsigned not null",
												  "varchar(255)",
												  "int",
												  "int",
												  "int",
												  "int",
												  "int",
												  "int",
												  "Blob",		//	VarBinary
												  "int",
												  "TinyBlob"	//	Binary
											  };
			PolygonColumnParams = new OdbcParameter[]
			{
				new OdbcParameter("Id", OdbcType.Int, 0, "Id"),
				new OdbcParameter("ObjId", OdbcType.Int, 0, "ObjId"),
				new OdbcParameter("WithinObjectId", OdbcType.Int, 0, "WithinObjectId"),
				new OdbcParameter("MaskFileName", OdbcType.VarChar, 255, "MaskFileName"),
				new OdbcParameter("X", OdbcType.Int, 0, "X"),
				new OdbcParameter("Y", OdbcType.Int, 0, "Y"),
				new OdbcParameter("Width", OdbcType.Int, 0, "Width"),
				new OdbcParameter("Height", OdbcType.Int, 0, "Height"),
				new OdbcParameter("NumPixels", OdbcType.Int, 0, "NumPixels"),
				new OdbcParameter("NumVertices", OdbcType.Int, 0, "NumVertices"),
				new OdbcParameter("Vertices", OdbcType.VarBinary, 65535, "Vertices"),
				new OdbcParameter("NumSwallowed", OdbcType.Int, 0, "NumSwallowed"),
				new OdbcParameter("SwallowedIds", OdbcType.Binary, 65535, "SwallowedIds")
			};
			PolygonColumnParams[0].IsNullable = false;
			PolygonColumnParams[1].IsNullable = false;
			for(int i=0; i<PolygonColumnParams.Length; i++)
				PolygonColumnParams[i].Direction = ParameterDirection.InputOutput;
		}
		public PolygonDbTable()
		{
			//	Assign the ColumnTypes and ColumnParams
			ColumnTypes = PolygonColumnTypes;
			ColumnParams = PolygonColumnParams;

			//	Create table and assign primary key columns
			InitializeTable("Poly", new int[] {0});
		}
		public PolygonDbTable(DbSource Connection) : base(Connection)
		{
			//	Assign the ColumnTypes and ColumnParams
			ColumnTypes = PolygonColumnTypes;
			ColumnParams = PolygonColumnParams;

			//	Create table and assign primary key columns
			InitializeTable("Poly", new int[] {0});

			//	Initialize the data adapter
			InitializeAdapters();
		}
		public DataRow AddDataRow( PolygonF pf )
		{
			DataRow row = Table.NewRow();
			InsertDataIntoRow(row, pf);
			Table.Rows.Add(row);
			row.AcceptChanges();
			return row;
		}
		public void InsertDataIntoRow( DataRow row, PolygonF pf )
		{
			//"Id",
			row[0] = pf.DbCache_UniqueId;
			//"ParsedObjectId",
			row[1] = pf.Owner.DbCache_UniqueId;
			//"WithinObjectId",
			row[2] = pf.Owner.Polygons.IndexOf(pf);
			//"MaskFileName",
			row[3] = pf.DbCache_MaskFileName;


			Rectangle rect = pf.GetBoundingRect(0.0f);
			//	X
			row[4] = rect.X;
			//  Y
			row[5] = rect.Y;
			//	Width
			row[6] = rect.Width;
			//	Height
			row[7] = rect.Height;
			//	NumPixels
			row[8] = pf.Pix.NumPts;
			//	NumVertices
			row[9] = pf.Count;
			//"Vertices"
			row[10] = pf.VerticesByteArray;
			//	NumSwallowed
			row[11] = pf.Swallowed.Count;
			//"SwallowedIds"
			row[12] = pf.SwallowedByteArray;
		}
	}
	#endregion
	#region Form Interfaces
	/// <summary>
	///	Minimal interface to FormParse.  Any command flowing from ObjectLib to the form must pass through this interface.
	/// </summary>
	public interface IFormParse
	{
		uint GetImgRectChangeCt();
		void PathToPointTable(PathF pf, PointF[] pbPoly);
		void ImgToPb(PointF[] input, PointF[] pbOutput);
		void ImgToPb(ContourF input, PointF[] pbOutput);
		int GetDisplayImageWidth();
		int GetDisplayImageHeight();
		void SetStatusBar(string text);
        void OutputConsoleMessage(string text);
	}
	public interface IFormPlot
	{
		System.Windows.Forms.PictureBox GetPb();
		ulong GetPbChangedCt();
		int GetWheelMinor();
		//Rect GetPbBoxPlotRect();
		//Rect GetPbDensityRect();
		//void GetPbAxisRect(ColorDeltaField field, out Rect[] axisRects, out bool[] drawVertical);
		//Rect[] GetHorAxRects();
	}
	#endregion
	#region SortingUtility
    /// <summary>
    /// Facilitates standard sorting of double floating point values when used inside of an ArrayList with the Sort routine, but also
    /// associates old indices with the new sorted indices so that another array can be re-indexed the same way.  Additionally,
    /// an array of DoubleSort objects can be "unsorted" to their original order (before the ArrayList was sorted).  This framework
    /// also becomes helpful when linking elements of an associated array across multiple Sort (or histogram normalization) schemas, 
    /// usually "Val" contains the sort key for a schema based on an associated array and "Ind" points to the index of an element in
    /// that array.
    /// </summary>
	[Serializable]
	public class DoubleSort : IComparable
	{
		public double Val;
		public int Ind;

		public DoubleSort(double val, int ind)
		{
			Val = val;
			Ind = ind;
		}
        /// <summary>
        /// Compares "Val".
        /// </summary>
        /// <param name="obj"></param>
        /// <returns>The result of a comparison between this.Val and obj.Val, -1 if this.Val is smaller, 0, or 1 if this.Val is greater.</returns>
		public int CompareTo(object obj)
		{
			if( Val > ((DoubleSort)obj).Val )
				return 1;
			if( Val < ((DoubleSort)obj).Val )
				return -1;
            if( Ind > ((DoubleSort)obj).Ind )
                return 1;
            if(Ind < ((DoubleSort)obj).Ind)
                return -1;
			return 0;
		}
        /// <summary>
        /// Gets the p-value of an object based on the old index "Ind".
        /// </summary>
        /// <param name="listLength"></param>
        /// <returns></returns>
		public double GetPval(int listLength)
		{
			return ((double)Ind+0.5) / (double)listLength;
		}
	}
    /// <summary>
    /// Facilitates standard sorting of floating point values when used inside of an ArrayList with the Sort routine, but also
    /// associates old indices with the new sorted indices so that another array can be re-indexed the same way.  Additionally,
    /// an array of FloatSort objects can be "unsorted" to their original order (before the ArrayList was sorted).  This framework
    /// also becomes helpful when linking elements of an associated array across multiple Sort (or histogram normalization) schemas, 
    /// usually "Val" contains the sort key for a schema based on an associated array and "Ind" points to the index of an element in
    /// that array.
    /// </summary>
	[Serializable]
	public class FloatSort : IComparable
	{
		public float Val;
		public int Ind;

		public FloatSort(float val, int ind)
		{
			Val = val;
			Ind = ind;
		}
        /// <summary>
        /// Compares "Val".
        /// </summary>
        /// <param name="obj"></param>
        /// <returns>The result of a comparison between this.Val and obj.Val, -1 if this.Val is smaller, 0, or 1 if this.Val is greater.</returns>
		public int CompareTo(object obj)
		{
			if( Val > ((FloatSort)obj).Val )
				return 1;
			if( Val < ((FloatSort)obj).Val )
				return -1;
            if(Ind > ((FloatSort)obj).Ind)
                return 1;
            if(Ind < ((FloatSort)obj).Ind)
                return -1;
			return 0;
		}
        /// <summary>
        /// Gets the p-value of an object based on the old index "Ind".
        /// </summary>
        /// <param name="listLength"></param>
        /// <returns></returns>
		public float GetPval(int listLength)
		{
			return ((float)Ind+0.5f) / (float)listLength;
		}
	}
    /// <summary>
    /// Facilitates standard sorting of integer values when used inside of an ArrayList with the Sort routine, but also
    /// associates old indices with the new sorted indices so that another array can be re-indexed the same way.  Additionally,
    /// an array of IntSort objects can be "unsorted" to their original order (before the ArrayList was sorted).  This framework
    /// also becomes helpful when linking elements of an associated array across multiple Sort (or histogram normalization) schemas, 
    /// usually "Val" contains the sort key for a schema based on an associated array and "Ind" points to the index of an element in
    /// that array.
    /// </summary>
	[Serializable]
	public class IntSort : IComparable
	{
		public float Val;
		public int Ind;

		public IntSort(int val, int ind)
		{
			Val = val;
			Ind = ind;
		}
        /// <summary>
        /// Compares "Val".
        /// </summary>
        /// <param name="obj"></param>
        /// <returns>The result of a comparison between this.Val and obj.Val, -1 if this.Val is smaller, 0, or 1 if this.Val is greater.</returns>
		public int CompareTo(object obj)
		{
			if( Val > ((IntSort)obj).Val )
				return 1;
			else if( Val < ((IntSort)obj).Val )
				return -1;
			return 0;
		}
        /// <summary>
        /// Gets the p-value of an object based on the old index "Ind".
        /// </summary>
        /// <param name="listLength"></param>
        /// <returns></returns>
		public float GetPval(int listLength)
		{
			return ((float)Ind+0.5f) / (float)listLength;
		}
	}
	
	#endregion
}